/**
 * 
 */
package soa.security.dao.impl;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.ldaptive.*;
import org.ldaptive.control.PagedResultsControl;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.ldap.core.*;
import org.springframework.ldap.query.ContainerCriteria;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.query.SearchScope;
import org.springframework.ldap.support.LdapNameBuilder;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Repository;
import soa.security.dao.AccountDao;
import soa.security.dom.CustomObjectDirectoryMapper;
import soa.security.domain.Account;
import soa.security.domain.Applsystem;
import soa.security.query.Convertor;

import javax.annotation.Resource;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author CreGu
 *
 */
@Repository
public class AccountDaoImpl extends SpringLdapDaoImpl<Account> implements
		AccountDao {

	@Resource
	private CustomObjectDirectoryMapper customObjectDirectoryMapper;

	public final static String PWD_ACCOUNT_LOCKED_TIME = "pwdAccountLockedTime";
	public final static String PWD_HISTORY = "pwdHistory";

	@Override
	public List<String> allApplsystems(Account account) {
		// TODO Auto-generated method stub
		return allAttrName(account.getDn(), "applsystems");
	}
	
	@Override
	public void create(Account t) {
		t.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
				.format(new Date()));
		Name id = t.getDn();
		DirContextAdapter context = new DirContextAdapter(id);

		customObjectDirectoryMapper.mapToLdapDataEntry(t, context);
		context.setAttributeValue("admined",
				BooleanUtils.toStringTrueFalse(t.isAdmined()).toUpperCase());
		context.setAttributeValue("disable",
				BooleanUtils.toStringTrueFalse(t.isDisable()).toUpperCase());
		context.setAttributeValue("expired",
				BooleanUtils.toStringTrueFalse(t.isExpired()).toUpperCase());
		context.setAttributeValue("credentialsNonExpired", BooleanUtils
				.toStringTrueFalse(t.isCredentialsNonExpired()).toUpperCase());

		ldapTemplate.bind(context);
	}

	@Override
	public void update(Account t) {
		DirContextAdapter context = (DirContextAdapter) ldapTemplate
				.lookupContext(t.getOldDn());

		if (StringUtils.isNotBlank(t.getBid()))
			context.setAttributeValue("bid", t.getBid());

		if (StringUtils.isNotBlank(t.getPersonBid()))
			context.setAttributeValue("personBid", t.getPersonBid());
		if (StringUtils.isNotBlank(t.getLoginTime())) {
			context.setAttributeValue("loginTime", t.getLoginTime());
		}
		context.setAttributeValue("admined",
				BooleanUtils.toStringTrueFalse(t.isAdmined()).toUpperCase());
		context.setAttributeValue("disable",
				BooleanUtils.toStringTrueFalse(t.isDisable()).toUpperCase());
		context.setAttributeValue("expired",
				BooleanUtils.toStringTrueFalse(t.isExpired()).toUpperCase());
		context.setAttributeValue("credentialsNonExpired", BooleanUtils
				.toStringTrueFalse(t.isCredentialsNonExpired()).toUpperCase());
		context.setAttributeValue("credentialsNonExpired", BooleanUtils
				.toStringTrueFalse(t.isCredentialsNonExpired()).toUpperCase());

		context.setAttributeValue("accountStatus",t.getAccountStatus());

		ldapTemplate.modifyAttributes(context);

		final String oldDn = context.getDn().toString();
		final String newDn = t.getDn().toString();

		if (!oldDn.equals(newDn)) {
			ldapTemplate.rename(oldDn, newDn);
		}
	}

	@Override
	public Account findByDn(String dn) {
		Account o = ldapTemplate.findByDn(LdapNameBuilder.newInstance(dn)
				.build(), Account.class);
		o.setUid(LdapUtils.getStringValue(o.getDn(), o.getDn().size() - 1));
		o.setOrganization(unitBelongs(dn));
		return o;
	}

	@Override
	public Account findByPrimaryKey(String baseDn, String id) {
		try {
			ContainerCriteria query = baseQuery(baseDn, SearchScope.ONELEVEL);
			query.and("uid").is(id);
			return ldapTemplate.searchForObject(query, new BaseContextMapper());
		} catch (Exception e) {
			return null;
		}
	}
	
	@Override
	public Page<Account> allAccounts(String baseDn, org.ldaptive.SearchScope searchScope, Map<String, Object> parameters,
			PageRequest pageRequest) throws Exception {
		// TODO Auto-generated method stub
		List<Account> results = new ArrayList<Account>();
		Connection conn = null;
		try {
			conn = pool.getConnection();
			conn.open();

			SearchOperation search = new SearchOperation(conn);

			LdapNameBuilder builder = LdapNameBuilder.newInstance(ldaptiveSupport.getBaseDn());
			if (StringUtils.isNotBlank(baseDn))
				builder.add(baseDn);

			String dn = builder.build().toString();
			
			//拼装过滤条件
			String filter = "";
			String objectclass = "(objectclass=zjtzsw-account)";
			String conditions = "";
			if (parameters != null) {
				String[] bids = (String[]) parameters.get("bid");
				if (bids != null && bids.length > 0) {
					if (StringUtils.isNotBlank(bids[0]))
						conditions = conditions + "(bid=*" + bids[0] + "*)";
				}
				
				String[] disables = (String[]) parameters.get("disable");
				if (disables != null && disables.length > 0) {
					if (StringUtils.isNotBlank(disables[0]))
						conditions = conditions + "(disable=" + disables[0].toUpperCase() + ")";
				}
			}
			filter = "(&" + objectclass + conditions + ")";
			
			System.out.println("ND = " + baseDn);
			System.out.println("filter = " + filter);
			

			SearchRequest request = new SearchRequest(dn, filter);
			request.setSearchScope(searchScope);
			request.setSizeLimit(0);

			Response<SearchResult> response = search.execute(request);
			int total = response.getResult().size();

			PagedResultsControl prc = new PagedResultsControl(pageRequest.getPageSize());
			String[] dns = null;

			byte[] cookie = null;
			for (int i = 0; i < pageRequest.getPageNumber() + 1; i++) {

				prc.setCookie(cookie);
				request.setControls(prc);

				response = search.execute(request);
				PagedResultsControl ctl = (PagedResultsControl) response.getControl(PagedResultsControl.OID);

				dns = response.getResult().getEntryDns();

				cookie = ctl.getCookie();
				if (cookie == null)
					break;
			}

			for (String b : dns) {
				results.add(findByDn(ldaptiveSupport.wrap4Spring(b)));
			}

			return new PageImpl(results, pageRequest, total);
		} catch (Exception e) {
			throw e;
		} finally {
			if (conn != null)
				conn.close();
		}
	}

	@Override
	public List<Account> allAccounts(String baseDn, SearchScope searchScope,
			Map<String, Object> parameters) {
		ContainerCriteria query = baseQuery(baseDn, searchScope);

		buildLikeCriteria(query, parameters, "bid");
		buildEqualsCriteria(query, parameters, "disable", new Convertor() {
			@Override
			public String convert(String orig) {
				return StringUtils.upperCase(orig);
			}
		});

		List<Account> list = ldapTemplate.search(query,
				new ContextMapper<Account>() {
					@Override
					public Account mapFromContext(Object ctx)
							throws NamingException {
						DirContextAdapter context = (DirContextAdapter) ctx;
						Account a = findByDn(context.getDn().toString());
						a.setPassword(null);
						a.setPwdLockout(isPwdLockout(a.getUid()));
						return a;
					}
				});

		return list;
	}

	@Override
	public List<Account> allAccounts(String baseDn, SearchScope searchScope,
			String personUid) {
		ContainerCriteria query = baseQuery(baseDn, searchScope);
		query.and("personBid").is(personUid);

		List<Account> list = ldapTemplate.search(query,
				new ContextMapper<Account>() {
					@Override
					public Account mapFromContext(Object ctx)
							throws NamingException {
						DirContextAdapter context = (DirContextAdapter) ctx;
						Account a = findByDn(context.getDn().toString());
						a.setPassword(null);
						return a;
					}
				});

		return list;
	}

	@Override
	public void pwdUpdate(String dn, String password) {
		DirContextOperations context = ldapTemplate.lookupContext(dn);

		context.setAttributeValue("userPassword", password);
		String pattern = "yyyy-MM-dd HH:mm:ss";
		context.setAttributeValue("passwordUpdateTime", new SimpleDateFormat(
				pattern).format(new Date()));
		
		Attribute attr = new BasicAttribute(PWD_HISTORY);
		ModificationItem item = new ModificationItem(
				DirContext.REMOVE_ATTRIBUTE, attr);
		ldapTemplate.modifyAttributes(dn,
				new ModificationItem[] { item });
		
		ldapTemplate.modifyAttributes(context);
	}

	@Override
	public void disable(String dn, boolean b) {
		DirContextOperations context = ldapTemplate.lookupContext(dn);

		if (!b) {
			Account a = findByDn(dn);
			if (isPwdLockout(a.getUid())) {

				Attribute attr = new BasicAttribute(PWD_ACCOUNT_LOCKED_TIME);
				ModificationItem item = new ModificationItem(
						DirContext.REMOVE_ATTRIBUTE, attr);
				ldapTemplate.modifyAttributes(dn,
						new ModificationItem[] { item });
			}
		}

		context.setAttributeValue("disable", BooleanUtils.toStringTrueFalse(b)
				.toUpperCase());
		ldapTemplate.modifyAttributes(context);
	}

	@Override
	public Account authenticate(String uid, String password) {
		Account t = ldapTemplate.authenticate(
				LdapQueryBuilder.query().where("objectclass")
						.is("zjtzsw-account").and("uid").is(uid), password,
				new AuthenticatedLdapEntryContextMapper<Account>() {
					@Override
					public Account mapWithContext(DirContext ctx,
							LdapEntryIdentification ldapEntryIdentification) {
						return findByDn(ldapEntryIdentification
								.getRelativeName().toString());
					}

				});

		DirContextOperations context = ldapTemplate.lookupContext(t.getDn());

		context.setAttributeValue("loginTime", new SimpleDateFormat(
				"yyyy-MM-dd HH:mm:ss").format(new Date()));
		ldapTemplate.modifyAttributes(context);
		return t;
	}

	public Account findByUid(String uid) {
		LdapQuery query = LdapQueryBuilder.query().where("objectclass")
				.is("zjtzsw-account").and("uid").is(uid);

		return ldapTemplate.searchForObject(query,
				new ContextMapper<Account>() {

					@Override
					public Account mapFromContext(Object ctx)
							throws NamingException {
						DirContextAdapter context = (DirContextAdapter) ctx;
						Account a = findByDn(context.getDn().toString());

						Object obj = context.getAttributes()
								.get("userpassword").get();
						byte[] storedPasswords = (byte[]) obj;

						a.setPasswordByte(storedPasswords);
						return a;
					}
				});
	}

	@Override
	public byte[] findStoredPassword(String uid) {
		LdapQuery query = LdapQueryBuilder.query().where("objectclass")
				.is("zjtzsw-account").and("uid").is(uid);

		Account ac = ldapTemplate.searchForObject(query,
				new ContextMapper<Account>() {

					@Override
					public Account mapFromContext(Object ctx)
							throws NamingException {
						Account a = new Account();
						DirContextAdapter context = (DirContextAdapter) ctx;

						Object obj = context.getAttributes()
								.get("userpassword").get();
						byte[] storedPasswords = (byte[]) obj;

						a.setPasswordByte(storedPasswords);
						return a;
					}
				});
		return ac.getPasswordByte();
	}

	@Override
	public boolean isPwdLockout(String uid) {
		Connection conn = null;
		boolean isPwdLockout = false;
		try {
			conn = pool.getConnection();
			conn.open();

			LdapNameBuilder builder = LdapNameBuilder
					.newInstance(ldaptiveSupport.getBaseDn());
			String filter = "(&(objectclass=zjtzsw-account)(uid=" + uid + "))";

			SearchOperation search = new SearchOperation(conn);
			SearchResult result = search.execute(
					new SearchRequest(builder.build().toString(), filter,
							PWD_ACCOUNT_LOCKED_TIME)).getResult();

			isPwdLockout = result.getEntry().getAttribute() != null;
		} catch (Exception e) {

			return isPwdLockout;
		} finally {
			if (conn != null)
				conn.close();
		}
		return isPwdLockout;
	}

	@Override
	public String getObjectClass() {
		return "zjtzsw-account";
	}

	@Override
	public void bind(String dn, String personId) {
		DirContextAdapter context = (DirContextAdapter) ldapTemplate
				.lookupContext(dn);

		context.setAttributeValue("personBid", personId);
		ldapTemplate.modifyAttributes(context);
	}
	
	@Override
	public void addApplsystems(Account account, Applsystem a) {
		// TODO Auto-generated method stub
		addAttrName(account.getDn(), "applsystems", a.getDn().toString());
	}
	
}
